﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Solidi
{
	public class Dependency : IDependency, IDependencyDecorator
	{
		private bool _dirty;
		private IDependencyRegistry _registry;
		private IDependencyRegistration _registration;
		private Dictionary<object, DependencyReference> _tokens = new Dictionary<object, DependencyReference>();

		public Dependency(IDependencyRegistry registry, IDependencyRegistration dependency)
		{
			_registry = registry;
			_registration = dependency;
		}

		public void Decorate(Func<IDependencyRegistration, IDependencyRegistration> decorator)
		{
			lock (this)
			{
				if (_dirty)
				{
					throw new Exception("Cannot modify a dependency that is in use.");
				}

				IDependencyKey key = _registration.Key;

				_registration = decorator(_registration);

				if (_registration.Key != key)
				{
					_registry.Remove(key, this);

					_registry.Add(_registration.Key, this);
				}
			}
		}

		public IDependencyReference Resolve(IDependencyContext context)
		{
			if (!_dirty)
			{
				lock (this)
				{
					_dirty = true;
				}
			}

			object token = _registration.Lifestyle.ResolveToken(context.Id);

			DependencyReference reference;

			lock (_tokens)
			{
				if (_tokens.TryGetValue(token, out reference))
				{
					reference.Increment();
				}
				else
				{
					object instance = _registration.Composer.Compose(context);

					if (instance != null)
					{
						reference = new DependencyReference(instance, token);

						_tokens.Add(token, reference);
					}
				}
			}

			return reference;
		}

		private DependencyDisposal ReleaseReference(DependencyReference reference)
		{
			lock (_tokens)
			{
				if (reference.Unreferenced)
				{
					return DependencyDisposal.ReferenceCountMismatch;
				}

				reference.Decrement();

				if (reference.Unreferenced)
				{
					if (_registration.Lifestyle.ReleaseToken(reference.Token))
					{
						_tokens.Remove(reference.Token);

						reference.Dispose();

						return DependencyDisposal.Disposed;
					}
				}
			}

			return DependencyDisposal.ReferenceCountDecremented;
		}

		public DependencyDisposal Release(IDependencyReference reference)
		{
			DependencyReference internalReference = reference as DependencyReference;

			if (internalReference == null)
			{
				throw new ArgumentOutOfRangeException("reference");
			}
			else
			{
				return ReleaseReference(internalReference);
			}
		}

		public void Dispose()
		{
			lock (_tokens)
			{
				DependencyReference[] references = _tokens.Select(e => e.Value).ToArray();

				foreach (DependencyReference reference in references)
				{
					reference.Dispose();
				}

				_tokens.Clear();
			}
		}
	}
}
